#!/usr/bin/python

"""
Defines the spoc graphical user interface.
"""

import os
import csv
import wx, wx.grid
from difflib import unified_diff

import spoc
from utils import *

ID_FAULTS_0 = 4010
ID_FAULTS_10 = 4011
ID_FAULTS_20 = 4012
ID_FAULTS_50 = 4013

ID_MAP_NAIVE = 4020
ID_MAP_SIMPLE = 4021
ID_MAP_SIZEAWARE = 4022
ID_MAP_SIZEAWARE_ROTATE = 4023

class MainFrame(wx.Frame):
    """
    Defines the GUI layout for the application
    """
    
    sysfile = ''
    infile = ''
    inpath = ''
    outfile = ''
    outpath = ''
    devfile = '.device'
    devpath = '.device'
    faultfile = ''
    num_faults = 0
    map_algorithm = 'simple'
    
    def __init__(self, parent, id, title):
        """
        initialize the window
        """
        self.graphviz_path = "/usr/bin/"
        self.expectfile = ''

        #CREATE THE WINDOW
        wx.Frame.__init__(self, parent, id, title, wx.DefaultPosition, wx.Size(1024, 768))
    
        #SET UP THE MENU BAR
        menubar = wx.MenuBar()
        
        #file = wx.Menu()
        #file.Append(100, 'New', 'Start a new project')
        #file.Append(101, 'Open', 'Open a project')
        #file.Append(102, 'Save', 'Save this project')
        #file.Append(103, 'Close', 'Close the current project')
        #file.Append(104, 'Exit', 'Exit the program')
        
        system = wx.Menu()
        system.Append(200, 'System from File...', 'Load a system')
        #system.Append(201, 'Export System...', 'Export a system')
        #system.Append(202, 'Edit Sytem (text)', 'Edit a system')
        #system.Append(203, 'Edit Sytem (graphical)', 'Edit a system')
        system.Append(204, 'New System...', 'Create a new system')
        
        data = wx.Menu()
        data.Append(300, '&Input File...', 'Open the input file')
        data.Append(301, '&Output File...', 'Set the output file')
        data.Append(302, '&Expect File...', 'Set the expect file')
        
        faults = wx.Menu()
        faults.Append(400, 'Fault Map from File...', 'Load a .fault')
        self.rnd_faults = wx.Menu()
        self.rnd_faults.AppendRadioItem(ID_FAULTS_0, '0')
        self.rnd_faults.AppendRadioItem(ID_FAULTS_10, '10')
        self.rnd_faults.AppendRadioItem(ID_FAULTS_20, '20')
        self.rnd_faults.AppendRadioItem(ID_FAULTS_50, '50')
        self.rnd_faults.Check(ID_FAULTS_0, True)
        #rnd_faults.Append(4014, 'Other...', kind=wx.ITEM_RADIO)
        faults.AppendMenu(401, 'Random Faults', self.rnd_faults)
        self.map_algs = wx.Menu()
        self.map_algs.AppendRadioItem(ID_MAP_NAIVE, 'naive')
        self.map_algs.AppendRadioItem(ID_MAP_SIMPLE, 'simple')
        self.map_algs.AppendRadioItem(ID_MAP_SIZEAWARE, 'sizeaware')
        self.map_algs.AppendRadioItem(ID_MAP_SIZEAWARE_ROTATE, 'sizeaware-rotate')
        self.map_algs.Check(ID_MAP_SIMPLE, True)
        faults.AppendMenu(402, 'Fault Avoidance Strategy', self.map_algs)
        
        #device = wx.Menu()
        #device.Append(500, 'Set Granularity', 'Set the overall granularity')
        #device.Append(501, 'Set Dimension', 'Set the overall dimension')
        #device.Append(502, 'Set Magnitude', 'Set the maximum operand size')
        #device.Append(503, 'Show Device', 'Display the current layout of the device')
        
        execute = wx.Menu()
        execute.Append(600, '&Run', 'Execute the current setup')
        
        help = wx.Menu()
        help.Append(700, '&About', 'About the HiPerCopS IDE')
        #help.Append(701, '&Documentation', 'Program documentation')
        
        #menubar.Append(file, '&File')
        menubar.Append(system, '&System')
        menubar.Append(data, '&Data')
        menubar.Append(faults, 'Fa&ults')
        #menubar.Append(device, 'De&vice')
        menubar.Append(execute, '&Execute')
        menubar.Append(help, '&Help')
        
        self.SetMenuBar(menubar)
        self.CreateStatusBar()
        
        #DO THE WINDOW LAYOUT
        self.left_panel= wx.Panel(self, -1, style=wx.SIMPLE_BORDER)
        self.right_panel= wx.Panel(self, -1, style=wx.SIMPLE_BORDER)
        
        system_box= wx.BoxSizer(wx.VERTICAL)
        self.system_label = wx.StaticText(self.left_panel, -1, 'System')
        system_box.Add(self.system_label, .5, wx.EXPAND)
        notebook= wx.Notebook(self.left_panel, -1, style=wx.NB_TOP)
        self.system_graph = wx.StaticBitmap(notebook, -1)
        #system_box.Add(self.system_graph, 1, wx.EXPAND)
        self.system_text = wx.TextCtrl(notebook, 1000, '', size=(-1, -1), style=wx.TE_MULTILINE | wx.TE_PROCESS_ENTER)
        self.system_text.SetEditable(False)
        #system_box.Add(self.system_text, 1, wx.EXPAND)
        notebook.AddPage(self.system_graph, "Graph")
        notebook.AddPage(self.system_text, "Text")
        system_box.Add(notebook, 1, wx.EXPAND)
        
        device_box= wx.BoxSizer(wx.VERTICAL)
        device_box.Add(wx.StaticText(self.right_panel, -1, 'Device'), .5, wx.EXPAND)
        self.device_grid = wx.grid.Grid(self.right_panel)
        self.device_grid.SetRowLabelSize(0)
        device_box.Add(self.device_grid, 1, wx.EXPAND)
        
        #project_box= wx.BoxSizer(wx.VERTICAL)
        #project_box.Add(wx.StaticText(self.right_panel, -1, 'Project'), .5, wx.EXPAND)
        #project_box.Add(wx.StaticText(self.right_panel, -1, ''), 1, wx.EXPAND)
        
        input_box= wx.BoxSizer(wx.VERTICAL)
        self.input_label = wx.StaticText(self.left_panel, -1, 'Input')
        input_box.Add(self.input_label, .5, wx.EXPAND)
        self.input_grid = wx.grid.Grid(self.left_panel)
        self.input_grid.SetRowLabelSize(0)
        input_box.Add(self.input_grid, 1, wx.EXPAND)

        output_box= wx.BoxSizer(wx.VERTICAL)
        self.output_label = wx.StaticText(self.right_panel, -1, 'Output')
        output_box.Add(self.output_label, .5, wx.EXPAND)
        notebook = wx.Notebook(self.right_panel, -1, style=wx.NB_TOP)
        self.output_grid = wx.grid.Grid(notebook, -1)
        self.output_grid.SetRowLabelSize(0)
        self.expect_text = wx.TextCtrl(notebook, 1000, '', size=(-1, -1), style=wx.TE_MULTILINE | wx.TE_PROCESS_ENTER)
        self.expect_text.SetEditable(False)
        notebook.AddPage(self.output_grid, "Results")
        notebook.AddPage(self.expect_text, "Diff")
        output_box.Add(notebook, 1, wx.EXPAND)

        left_box= wx.BoxSizer(wx.VERTICAL)
        left_box.Add(system_box, 1, wx.EXPAND)
        left_box.Add(input_box, 1, wx.EXPAND)
        self.left_panel.SetSizer(left_box)

        right_box= wx.BoxSizer(wx.VERTICAL)
        #right_box.Add(project_box, 1, wx.EXPAND)
        right_box.Add(device_box, 1, wx.EXPAND )
        right_box.Add(output_box, 1, wx.EXPAND )
        self.right_panel.SetSizer(right_box)
        
        box= wx.BoxSizer(wx.HORIZONTAL)
        box.Add(self.left_panel, 1, wx.EXPAND | wx.ALL, 3)
        box.Add(self.right_panel, 1, wx.EXPAND | wx.ALL, 3)
        
        #BIND EVENTS TO ACTIONS
        #system
        self.Bind(wx.EVT_MENU, self.SystemFromFile, id=200)
        self.Bind(wx.EVT_MENU, self.NewSystem, id=204)
        self.Bind(wx.EVT_MENU, self.SetInputFile, id=300)
        self.Bind(wx.EVT_MENU, self.SetOutputFile, id=301)
        self.Bind(wx.EVT_MENU, self.SetExpectFile, id=302)
        self.Bind(wx.EVT_MENU, self.SetFaultFile, id=400)
        self.Bind(wx.EVT_MENU, self.SetNumFaults, id=ID_FAULTS_0, id2=ID_FAULTS_50)
        self.Bind(wx.EVT_MENU, self.SetMappingAlgorithm, id=ID_MAP_NAIVE, id2=ID_MAP_SIZEAWARE_ROTATE)
        self.Bind(wx.EVT_MENU, self.Run, id=600)
        self.Bind(wx.EVT_MENU, self.About, id=700)
        #self.Bind(wx.EVT_MENU, self.Documentation, id=701)
        self.device_grid.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, self.ExpandCell)
        
        #SIZE AND CENTER
        self.SetSizer(box)
        self.Center()
        
    def SystemFromFile(self, event):
        dlg = wx.FileDialog(self, "Add System", os.getcwd(), "", "*.dot", wx.OPEN)
        if dlg.ShowModal() == wx.ID_OK:
            path = dlg.GetPath()
            self.sysfile = os.path.basename(path)
            self.SetStatusText("System added: %s" % self.sysfile)
        dlg.Destroy()
        self.system_text.Clear()
        f = open(path, "r")
        lines = f.readlines()
        for line in lines:
            self.system_text.AppendText(line)
        f.close()
        os.spawnl(os.P_WAIT, self.graphviz_path + "dot", "dot", path, "-Tjpg", "-o"+os.getcwd()+"/.system")
        self.system_graph.SetBitmap(wx.Image(os.getcwd()+"/.system", wx.BITMAP_TYPE_JPEG).ConvertToBitmap())

        self.system_label.SetLabel('System: ' + self.sysfile)
        
    def NewSystem(self, event):
        os.spawnl(os.P_WAIT, self.graphviz_path + 'dotty')
        
    def SetInputFile(self, event):
        #get the input file name
        dlg = wx.FileDialog(self, "Input File", os.getcwd(), "", "*.csv", wx.OPEN)
        if dlg.ShowModal() == wx.ID_OK:
            self.inpath = dlg.GetPath()
            self.infile = os.path.basename(self.inpath)
            self.SetStatusText("Input file specified: %s" % self.infile)
        dlg.Destroy()
        
        #display the input file in the input frame
        #open the file
        inreader = csv.reader(open(self.inpath))
        rownum = -1
        #go through each entry in the file
        for row in inreader:
            #the first row contains the column headings
            if rownum == -1:
                #create the grid in the GUI
                self.input_grid.DeleteRows(0, self.input_grid.GetNumberRows(), False)
                self.input_grid.CreateGrid(0, len(row))
                #fill the column headings with the correct names
                for idx in range(len(row)):
                    self.input_grid.SetColLabelValue(idx, row[idx])
            #the subsequent rows contain the data
            else:
                #add a new row to the grid
                self.input_grid.AppendRows(1, False)
                #fill the new row with data
                for idx in range(len(row)):
                    self.input_grid.SetCellValue(rownum, idx, row[idx])
            rownum += 1
        self.input_grid.AutoSize()

        self.input_label.SetLabel('Input: ' + self.infile)
        
    def SetOutputFile(self, event):
        dlg = wx.FileDialog(self, "Output File:", os.getcwd(), "", "*.csv", wx.OPEN)
        if dlg.ShowModal() == wx.ID_OK:
            path = dlg.GetPath()
            self.outfile = os.path.basename(path)
            self.SetStatusText("Output file chosen: %s" % self.outfile)
        dlg.Destroy()

        #self.output_label.Destroy()
        #self.output_label = wx.StaticText(self.right_panel, -1, 'Output: ' + self.outfile)
        self.output_label.SetLabel('Output: ' + self.outfile)
    
    def SetExpectFile(self, event):
        dlg = wx.FileDialog(self, "Expect File:", os.getcwd(), "", "*.csv", wx.OPEN)
        if dlg.ShowModal() == wx.ID_OK:
            path = dlg.GetPath()
            self.expectfile = os.path.basename(path)
            self.SetStatusText("Output file chosen: %s" % self.expectfile)
        dlg.Destroy()
        
        self.output_label.SetLabel('Expect: ' + self.expectfile)

    def SetFaultFile(self, event):
        #get the fault file name
        dlg = wx.FileDialog(self, "Fault File", os.getcwd(), "", "*.fault", wx.OPEN)
        if dlg.ShowModal() == wx.ID_OK:
            path = dlg.GetPath()
            self.faultfile = os.path.basename(path)
            self.SetStatusText("Fault file selected: %s" % self.faultfile)
        dlg.Destroy()
        #TODO map the faults now?

    def SetNumFaults(self, event):
        for item in self.rnd_faults.GetMenuItems():
            if item.IsChecked():
                self.num_faults = int(item.GetLabel())
        self.SetStatusText("%s random faults injected" % self.num_faults)
    
    def SetMappingAlgorithm(self, event):
        for item in self.map_algs.GetMenuItems():
            if item.IsChecked():
                self.map_algorithm = item.GetLabel()
        self.SetStatusText("Set mapping algorithm: %s" % self.map_algorithm)

    def ExpandCell(self, event):
        row = event.GetRow()
        col = event.GetCol()
        
        celltype = self.device_grid.GetCellValue(row, col)

        cellframe = ExpandedCell()
        cellframe.setType(celltype)
        cellframe.populateGrid()
        cellframe.Show()

    def Run(self, event):
        #check for necessary inputs
        if len(self.sysfile) == 0:
            wx.MessageBox('No system is mapped to the device.', 'Error')
        elif len(self.infile) == 0:
            wx.MessageBox('No input file has been specified.', 'Error')
        elif len(self.outfile) == 0:
            wx.MessageBox('No output file has been specified.', 'Error')
        else:
            #compile command
            args = ["-s" + self.sysfile, "-i" + self.infile, "-o" + self.outfile]
            if self.faultfile != '':
                args.append("-F" + self.faultfile)
            if self.num_faults != 0:
                args.append("-f" + str(self.num_faults))
            args.append("-m" + self.map_algorithm)
                
            self.SetStatusText("Executing...")
                
            #run command
            try:
                spoc.main(args)
            except Exception as inst:
                self.SetStatusText("ERROR!")
                return
            
            self.SetStatusText("Getting results...")
            
            #update device frame
            #open the file
            devreader = csv.reader(open(self.devpath))
            rownum = 0
            #go through each entry
            for row in devreader:
                #the first row has the column headings
                if rownum == 0:
                    self.device_grid.DeleteRows(0, self.device_grid.GetNumberRows(), False)
                    self.device_grid.CreateGrid(0, len(row))
                    #fill the column headings with the correct names
                    for idx in range(len(row)):
                        self.device_grid.SetColLabelValue(idx, '')
                #add a new row to the grid
                self.device_grid.AppendRows(1, False)
                #fill the new row with the data
                for idx in range(len(row)):
                    self.device_grid.SetCellValue(rownum, idx, row[idx])
                rownum += 1
            self.device_grid.AutoSizeColumns()
            
            #update output frame
            #open the file
            outreader = csv.reader(open(self.outfile))
            rownum = -1
            #go through each entry
            for row in outreader:
                #the first row has the column headings
                if rownum == -1:
                    #create the grid in the GUI
                    self.output_grid.DeleteRows(0, self.output_grid.GetNumberRows(), False)
                    self.output_grid.CreateGrid(0, len(row))
                    #fill in the column headings with the correct names
                    for idx in range(len(row)):
                        self.output_grid.SetColLabelValue(idx, row[idx])
                #the rest of the rows are data
                else:
                    #add a new row to the grid
                    self.output_grid.AppendRows(1, False)
                    #fill the new row with the data
                    for idx in range(len(row)):
                        self.output_grid.SetCellValue(rownum, idx, row[idx])
                rownum += 1
            self.output_grid.AutoSizeColumns()
            
            #update the diff frame
            self.expect_text.Clear()

            if self.expectfile != '':
                outreader = csv.reader(open(self.outfile))
                expectreader = csv.reader(open(self.expectfile))

                actual = []
                expected = []
                for actual_row, expected_row in zip(outreader, expectreader):
                    actual.append(' '.join(actual_row) + '\n')
                    expected.append(' '.join(expected_row) + '\n')

                for line in unified_diff(actual, expected):
                    self.expect_text.AppendText(line)
            else:
                self.expect_text.AppendText("No expect file to diff against.")

            self.SetStatusText("Finished")
            
    def About(self, event):
        description = """A Medium-Grain Reconfigurable Hardware Simulation Tool"""

        licence = """GPL V3"""

        info = wx.AboutDialogInfo()

        info.SetName('HiPerCopS IDE')
        info.SetVersion('0.1.1')
        info.SetDescription(description)
        info.SetCopyright('(C) 2010 Kylan Robinson')
        info.SetWebSite('http://eecs.wsu.edu/~krobinso')
        info.SetLicence(licence)
        info.AddDeveloper('Kylan Robinson')

        wx.AboutBox(info)
        
class App(wx.App):
    """
    create the application
    """
    def OnInit(self):
        frame= MainFrame(None, 1, 'HiPerCopS - IDE')
        frame.Show(True)
        return True

class ExpandedCell(wx.Frame):
    
    def __init__(self):
        load_libs()
        self.celltype = ''
        wx.Frame.__init__(self, None, wx.ID_ANY, "", wx.DefaultPosition, wx.Size(230, 150))
    
    def setType(self, celltype):
        self.SetTitle(celltype)
        self.celltype = celltype
    
    def populateGrid(self):
        #get the cell's details from the library
        cell = cell_type_dict[self.celltype]
        
        #get the cell's dimensions
        numrows = len(cell.elements)
        numcols = len(cell.elements[0])
        
        #create grid
        panel = wx.Panel(self, wx.ID_ANY)
        self.grid = wx.grid.Grid(panel)
        self.grid.CreateGrid(0, numcols)
        self.grid.SetRowLabelSize(0)
        
        for col in range(numcols):
            self.grid.SetColLabelValue(col, '')
        for row in range(numrows):
            self.grid.AppendRows(1, False)
            self.grid.SetRowLabelValue(row, '')
        
        #fill the grid
        for row in range(numrows):
            for col in range(numcols):
                self.grid.SetCellValue(row, col, cell.elements[row][col])
       
        self.grid.AutoSize()
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.grid)
        panel.SetSizer(sizer) 
        
        self.grid.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, self.ExpandElement)
    
    def ExpandElement(self, event):
        row = event.GetRow()
        col = event.GetCol()
        
        elemtype = self.grid.GetCellValue(row, col)

        elemframe = ExpandedElement()
        elemframe.setType(elemtype)
        elemframe.populateGrid()
        elemframe.Show()

class ExpandedElement(wx.Frame):
    
    def __init__(self):
        load_libs()
        self.elemtype = ''
        wx.Frame.__init__(self, None, wx.ID_ANY, "", wx.DefaultPosition, wx.Size(225, 415))
    
    def setType(self, elemtype):
        self.SetTitle(elemtype)
        self.elemtype = elemtype
    
    def populateGrid(self):
        #get the cell's details from the library
        elem = element_type_dict[self.elemtype]
        
        #get the cell's dimensions
        numrows = len(elem)
        numcols = len(elem[0])
        
        #create grid
        panel = wx.Panel(self, wx.ID_ANY)
        self.grid = wx.grid.Grid(panel)
        self.grid.CreateGrid(0, numcols)
        self.grid.SetRowLabelSize(0)
        
        for col in range(numcols):
            self.grid.SetColLabelValue(col, '')
        for row in range(numrows):
            self.grid.AppendRows(1, False)
            self.grid.SetRowLabelValue(row, '')
        
        #fill the grid
        for row in range(numrows):
            for col in range(numcols):
                self.grid.SetCellValue(row, col, elem[row][col])

        self.grid.AutoSize()
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.grid)
        panel.SetSizer(sizer)
        
def main():
    """
    the main method
    """
    app= App(0)
    app.MainLoop()

if __name__ == '__main__':
    """
    call to main
    """
    main()
